<?php
/***************************************************************************
 *   Copyright (C) 2007 by Andrea Blankenstijn, David Dieulivol            *
 *   darkan9el@gmail.com, daviddieulivol5@hotmail.com                      *
 *                                                                         *
 *   All rights reserved.                                                  *
 *   This work is released under the terms of the New BSD License.         *
 *   See LICENSE.BSD file for licensing details                            *
 ***************************************************************************/
/* * *
 * $Id: photo.class.php 51 2007-09-16 21:10:27Z DarkAn9el $
 * * */

/**
 * \class Photo
 * \version $Revision: 51 $
 * \date $Date: 2007-09-16 21:10:27 +0000 (Sun, 16 Sep 2007) $
 * \author Andrea Blankenstijn
 * \author David Dieulivol
 * \brief Classe d'accès aux données des images et de leur miniature
 */

class Photo
{
	private $date;         ///< date de publication
	private $description;  ///< description de la photo
	private $id;           ///< identifiant de la photo
	private $isOnline;     ///< photo visible par le public
	private $name;         ///< nom de la photo
	private $pdoProxy;     ///< instance de PdoProxy
	private $sha1Sum;      ///< somme SHA1 de l'image
	private $sysFilePath;  ///< chemin du fichier de l'image par rapport à la racine système
	private $sysThumbPath; ///< chemin du fichier de la miniature par rapport à la racine système
	private $tPhotos;      ///< nom de la table photos avec préfix
	private $type;         ///< format d'image
	private $webFilePath;  ///< chemin du fichier de l'image par rapport à la racine web
	private $webThumbPath; ///< chemin du fichier de la miniature par rapport à la racine web

	/**
	 * \brief Retourne un tableau de toutes les images
	 *
	 * Retourne un tableau contenant toutes les informations des images.
	 * Remarque: aucun champ n'est traité avec stripslashes, cela peut-être nécessaire
	 * pour name et description.
	 * \param[in] $dbh instance PdoProxy
	 * \param[in] $onlineOnly ne retourner que les photos en ligne (bool)
	 * \param[in] $sortCol champs utilisé pour ordrer le jeu de résultats (int)
	 *   - 0 → par ID
	 *   - 1 → par nom
	 *   - 2 → par description
	 *   - 3 → par somme SHA1
	 *   - 4 → par date d'ajout
	 *   - 5 → par ID d'auteur
	 *   - 6 → par type
	 *   - 7 → par ID de copyright
	 * \param[in] $limit limiter le nombre de résultats retournés (SQL: LIMIT) (int)
	 * \param[in] $offset ignorer les X premiers résultats (SQL: OFFSET) (int)
	 * \return tableau associatif des images sélectionnées
	 * \return FALSE si la requête n'a retourné aucun résultat
	 */
	public static function listPhotos(PdoProxy $dbh, $onlineOnly = TRUE, $sortCol = 0, $limit = null, $offset = null)
	{
		$tPhotos           = $dbh->getPrefix() .'photos';
		$tPhotos2Galleries = $dbh->getPrefix() .'photos2galleries';

		// Close WHERE
		if($onlineOnly)
			$where = 'WHERE `isOnline`=1';

		// Close ORDER BY
		if(is_int($sortCol))
		{
			$columns = array(
					'`idPhotos`', '`name`', '`description`', '`sha1Sum`',
					'`date`', '`idAuthors`', '`type`', '`idCopyrights`'
				);

			$order   = 'ORDER BY '. $columns[$sortCol];
		}

		// Close LIMIT/OFFSET
		if(is_int($limit))
		{
			$limit = 'LIMIT '. $limit;
			if(is_int($offset))
				$limit .= ' OFFSET '. $offset;
		}


		// Chargement des données des images
		$queryStr  = 'SELECT * FROM `'.$tPhotos.'` '.$where.' '.$order.' '.$limit.';';
		if($query  = $dbh->query($queryStr))
		{
			$list  = $query->fetchAll(PDO::FETCH_ASSOC);

			// Chargement des IDs des galeries aux quelles chaque image appartient
			$query = $dbh->prepare('SELECT `idGalleries` FROM `'.$tPhotos2Galleries.'` WHERE `idPhotos`=:id;');
			foreach($list as $key => $item)
			{
				$id = (int) $item['idPhotos'];
				$query->bindValue(':id', $id, PDO::PARAM_INT);

				if($query->execute())
				{
					while($id = $query->fetchColumn())
						$list[$key]['inGallery'][] = $id;
				}
			}

			return $list;
		}

		return FALSE;
	}

	/**
	 * \brief Constructeur de la classe. Charge les données d'une
	 * image si l'on passe son identifiant comme second argument.
	 * \param[in] $dbh instance PDO
	 * \param[in] $idPhoto identifiant de la photo à charger
	 * \see load
	 */
	public function __construct(PdoProxy $dbh, $idPhoto = NULL)
	{
		$this->tPhotos  = $dbh->getPrefix() .'photos';
		$this->pdoProxy = $dbh;

		if(!empty($idPhoto))
			$this->load($idPhoto);
	}

	/**
	 * \brief Méthode d'accès à l'attribut date
	 * \return la date d'ajout de la photo
	*/
	public function getDate()
	{
		return $this->date;
	}

	/**
	 * \brief Méthode d'accès à l'attribut description
	 * \return description de la photo
	 */
	public function getDescription()
	{
		return $this->description;
	}

	/**
		* \brief Méthode d'accès à l'attribut id
		* \return ID de la photo
	*/
	public function getId()
	{
		return $this->id;
	}

	/**
	 * \brief Méthode d'accès à l'attribut name
	 * \return nom de la photo
	*/
	public function getName()
	{
		return $this->name;
	}

	/**
	 * \brief Méthode d'accès à l'attribut webFilePath
	 * \return chemin d'accès web de la photo
	 */
	public function getImageWebPath()
	{
		return $this->webFilePath;
	}

	/**
	 * \brief Méthode d'accès à l'attribut webThumbPath
	 * \return chemin d'accès web de la miniature
	 */
	public function getThumbnailWebPath()
	{
		return $this->webThumbPath;
	}

	/**
	 * \brief Méthode de génération des miniatures
	 *
	 * «Super-méthode» pour makeThumbnailGd(), makeThumbnailImagick() et makeThumbnailSysImageMagick()
	 * Essais d'utiliser par ordre de préférence Imagick, ImageMagick ou GD selon leur disponibilité
	 * \param[in] $force forcer la génération de la miniature même si elle existe déjà
	 * \param[in] $zoom autorise la création de miniature plus grande que l'image d'origine (non implémenté)
	 * \exception Exception Aucune méthode utilisable pour le redimensionnement
	 */
	public function makeThumbnail($force = FALSE, $zoom = FALSE)
	{
		if(!file_exists($this->sysThumbPath) OR $force)
		{
			if(extension_loaded('imagick'))
				$this->makeThumbnailImagick();
			if(!(CONF_SYS_CONVERT === FALSE))
				$this->makeThumbnailSysImageMagick();
			elseif(extension_loaded('gd'))
				$this->makeThumbnailGd();
			else
				throw new Exception('Aucune méthode de création de miniature n\'est disponible', 4);
		}
	}

	/**
	 * \brief Charge l'image ayant l'identifiant passé en argument.
	 * \param[in] $id identifiant de l'image
	 * \exception Exception la requête n'a renvoyé aucun résultat
	 */
	 public function load($id)
	 {
		$request = $this->pdoProxy->prepare('SELECT * FROM `'.$this->tPhotos.'` WHERE `idPhotos` = :id;');
		$request->bindValue(':id', $id, PDO::PARAM_INT);
		$request->execute();

		if($row = $request->fetch())
		{
			$this->id          = $id;
			$this->date        = $row['date'];
			$this->description = stripslashes($row['description']);
			$this->isOnline    = $row['isOnline'];
			$this->name        = stripslashes($row['name']);
			$this->sha1Sum     = $row['sha1Sum'];
			$this->type        = $row['type'];
		}
		else
			throw new Exception('La photo '. $id .' n\'a pas pu être chargée');

		// Chemins pour l'image
		$relativePath        = CONF_IMG_PATH .'/'. $this->sha1Sum .'.'. $this->type;
		$this->webFilePath   = CONF_WEB_WORKDIR .'/'. $relativePath;
		$this->sysFilePath   = CONF_SYS_WORKDIR .'/'. $relativePath;

		// Chemins pour la miniature
		$relativePath        = CONF_THUMB_PATH .'/'. $this->sha1Sum .'.jpg';
		$this->webThumbPath  = CONF_WEB_WORKDIR .'/'. $relativePath;
		$this->sysThumbPath  = CONF_SYS_WORKDIR .'/'. $relativePath;

		$this->makeThumbnail();
	 }

	/**
	 * \brief Génération de la miniature de l'image avec la bibliothèque GD
	 * \see makeThumbnail
	 * \exception Exception erreur lors de l'ouverture du fichier
	 * \exception Exception erreur lors de l'enregistrement
	 */
	private function makeThumbnailGd()
	{
		// Chargement de l'image
		if($this->type == 'jpg')
		{
			if(!$srcImg = ImageCreateFromJpeg($this->sysFilePath))
				throw new Exception('GD: erreur lors de l\'ouverture de l\'image JPEG', 1);
		}
		else if($this->type == 'png')
		{
			if(!$srcImg = ImageCreateFromPng($this->sysFilePath))
				throw new Exception('GD: erreur lors de l\'ouverture de l\'image PNG', 1);
		}

		// Dimensions d'origine
		$size      = GetImageSize($this->sysFilePath);
		$srcWidth  = $size[0];
		$srcHeight = $size[1];

		// Dimensions miniature
		$thumbWidth  = CONF_THUMB_SIZE;
		$thumbHeight = round(($thumbWidth / $srcWidth) * $srcHeight);
		$thumbImg    = ImageCreateTrueColor($thumbWidth,$thumbHeight);

		// Redimensionnement et enregistrement de la miniature
		ImageCopyResampled($thumbImg, $srcImg, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $srcWidth, $srcHeight);
		if(!ImageJpeg($thumbImg, $this->sysThumbPath))
			throw new Exception('GD: erreur lors de l\'enregistrement de la miniature', 1);
	}

	/**
	 * \brief Génération de la miniature de l'image avec Imagick
	 *
	 * cf. http://pecl.php.net/imagick
	 * \see makeThumbnail
	 */
	private function makeThumbnailImagick()
	{
		// La version 2 de Imagick fournit une classe
		if(class_exists('Imagick'))
		{
			$this->makeThumbnailImagick2();
		}
		// La version 0.9.x ne fournit pas de classe
		else
		{
			$this->makeThumbnailImagick09();
		}
	}

	/**
	 * \brief Génération de la miniature de l'image avec Imagick 2
	 *
	 * \see makeThumbnailImagick
	 * \exception Exception erreur lors de l'ouverture du fichier
	 * \exception Exception erreur lors du redimensionnement
	 * \exception Exception erreur lors de l'enregistrement
	 */
	private function makeThumbnailImagick2()
	{
		$imagick = new Imagick();

		// Chargement de l'image
		if(!$imagick->readImage($this->sysFilePath))
			throw new Exception('Imagick: erreur lors de l\'ouverture de l\'image', 2);

		// Miniaturisation
		if(!$imagick->thumbnailImage(CONF_THUMB_SIZE, NULL))
			throw new Exception('Imagick: erreur lors du redimensionnement de l\'image', 2);

		// Enregistrement de la miniature
		if(!$imagick->writeImage($this->sysThumbPath))
			throw new Exception('Imagick: erreur lors de l\'enregistrement de la miniature', 2);

		// DESTROYER !!
		$imagick->destroy();
	}

	/**
	 * \brief Génération de la miniature de l'image avec Imagick 0.9.x
	 * \exception Exception erreur lors de l'ouverture du l'image
	 * \exception Exception erreur lors du redimensionnement
	 * \exception Exception erreur lors de l'enregistrement de la miniature
	 * \see makeThumbnailImagick
	 */
	private function makeThumbnailImagick09()
	{
		// Chargement de l'image
		if(!$srcImg = imagick_readImage($this->sysFilePath))
			throw new Exception('Imagick: erreur lors de l\'ouverture de l\'image', 2);

		// Redimensionnement
		if(!imagick_resize($srcImg, CONF_THUMB_SIZE, CONF_THUMB_SIZE, IMAGICK_FILTER_GAUSSIAN, 0))
			throw new Exception('Imagick: erreur lors du redimensionnement de l\'image', 2);

		// Enregistrement de la miniature
		if(!imagick_writeImage($srcImg, $this->sysThumbPath))
			throw new Exception('Imagick: erreur lors de l\'enregistrement de la miniature', 2);
	}

	/**
	 * \brief Génération de la miniature de l'image avec ImageMagick du système
	 *
	 * cf. http://www.imagemagick.org
	 * \see makeThumbnail
	 * \return retour commande système
	 * \exception Exception ImageMagick à retourné une valeur autre que 0
	 */
	private function makeThumbnailSysImageMagick()
	{
		$cmdOutput   = array();
		$returnValue = 1;

		// Protège la chaîne pour une utilisation dans le shell
		$args = escapeshellarg(CONF_THUMB_SIZE. 'x'. CONF_THUMB_SIZE) .' '.
		        escapeshellarg($this->sysFilePath) .' '.
		        escapeshellarg($this->sysThumbPath);

		// Exécute convert de ImageMagick pour créer la miniature
		exec('convert -resize '. $args, $cmdOutput, $returnValue);

		$cmdOutput = implode("\n", $cmdOutput);

		if($returnValue != 0)
			throw new Exception('ImageMagick: la commande à retourné '. $returnValue ."\nsortie :\n". $cmdOutput, 3);
	}
}
?>
